<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Three.js CSS3D Example with Multiple Objects</title>
  <style>
    body {
      margin: 0;
    }

    canvas {
      display: block;
    }

    .context-menu {
      position: absolute;
      background-color: rgb(173, 47, 47);
      border: 1px solid #ccc;
      box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
      z-index: 1000;
    }

    .context-menu-item {
      padding: 8px 12px;
      cursor: pointer;
    }

    .context-menu-item:hover {
      background-color: #eee;
    }
  </style>
  <link rel="stylesheet" href="../bigScreen/index.css">
</head>

<body>
  <div id="labels"></div>

  <script type="importmap">
    {
        "imports": {
            "three": "../three/v170/three.module.min.js",
            "three/addons/": "../three/v170/"
        }
    }
    </script>

  <script type="module">
    import * as THREE from 'three';
    import { CSS2DRenderer, CSS2DObject } from "three/addons/renderers/CSS2DRenderer.js";
    import { CSS3DRenderer, CSS3DObject } from "three/addons/renderers/CSS3DRenderer.js";
    import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
    import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
    import { DragControls } from 'three/addons/controls/DragControls.js';
    import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

    // 创建场景
    const scene = new THREE.Scene();
    let controls;
    const cur_ratio = 0.02;
    let labelsArr = [];// 标签数组
    let middle_shebei = {};
    let raycaster = null;
    let mouse = null;

    // 创建相机
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    // camera.position.z = 10;
    camera.rotation.set(0, 0, 0);
    camera.position.set(0, 0, 15);// 正视图

    raycaster = new THREE.Raycaster();
    mouse = new THREE.Vector2();

    // 创建渲染器
    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.domElement.style.position = 'absolute';
    // renderer.domElement.style.zIndex = 10;
    document.body.appendChild(renderer.domElement);

    // 创建坐标轴辅助对象
    var axesHelper = new THREE.AxesHelper(1000);
    scene.add(axesHelper);

    // 添加控制器
    controls = new OrbitControls(camera, renderer.domElement);
    controls.dampingFactor = 0.25;
    // 添加平移功能
    controls.enablePan = true;

    // 创建CSS3D渲染器
    const cssRenderer = new CSS3DRenderer();
    cssRenderer.setSize(window.innerWidth, window.innerHeight);
    cssRenderer.domElement.style.position = 'absolute';
    cssRenderer.domElement.style.top = 0;
    cssRenderer.domElement.style.left = 0;
    // cssRenderer.domElement.style.zIndex = 15;
    // 不遮挡canvas和threejs的点击事件;导致使用CSS2DObject添加的HTML标签点击事件无效;
    cssRenderer.domElement.style.pointerEvents = 'none';
    document.body.appendChild(cssRenderer.domElement);

    // 纹理加载器
    const textureLoader = new THREE.TextureLoader();

    createObjectWithLabel({
      imgURL: './images/souce_05.png',
      width: 1393,
      height: 972,
      x: 0,
      y: 0,
      z: 0,
      renderOrder: 1, // 设置渲染顺序
      labelObj: {}
    })
    createObjectWithLabel({
      imgURL: './images/souce_01.png',
      width: 107,
      height: 96,
      x: -10.7,
      y: -2.4,
      z: 0,
      renderOrder: 3, // 设置渲染顺序
      labelObj: {
        type: 'machine_1',
        name: '1号机台',
        position: [-10.7, -2.4, 0],// 悬浮窗位置信息
      },
    })
    createObjectWithLabel({
      imgURL: './images/souce_03.png',
      width: 111,
      height: 102,
      x: 0.908999,
      y: -2.3,
      z: 0,
      renderOrder: 6, // 设置渲染顺序
      labelObj: {
        type: 'machine_2',
        name: '4号机台',
        position: [0.908999, -2.3, 0],// 悬浮窗位置信息
      },
    })
    createObjectWithLabel({
      imgURL: './images/souce_03.png',
      width: 111,
      height: 102,
      x: -1.199999,
      y: -0.01222,
      z: 0,
      renderOrder: 7, // 设置渲染顺序
      labelObj: {
        type: 'machine_2',
        name: '5号机台',
        position: [-1.199999, -0.01222, 0],// 悬浮窗位置信息
      },
    })
    createObjectWithLabel({
      imgURL: './images/souce_03.png',
      width: 111,
      height: 102,
      x: 6.2999,
      y: -0.2000999,
      z: 0,
      renderOrder: 9, // 设置渲染顺序
      labelObj: {
        type: 'machine_2',
        name: '7号机台',
        position: [-0.3999999, 0.2434999, 0],// 悬浮窗位置信息
      },
    })

    // 创建多个图形和标签的函数
    function createObjectWithLabel(obj) {
      const textureLoader = new THREE.TextureLoader();
      const texture = textureLoader.load(obj.imgURL);
      texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
      texture.minFilter = THREE.LinearMipmapLinearFilter;
      texture.magFilter = THREE.LinearFilter;
      texture.generateMipmaps = true;
      texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping;

      // texture.minFilter = THREE.LinearFilter; // 尝试使用线性过滤
      texture.magFilter = THREE.LinearFilter; // 同样使用线性过滤

      const cur_widht = obj.width;
      const cur_height = obj.height;
      const geometry = new THREE.PlaneGeometry(cur_widht * cur_ratio, cur_height * cur_ratio); // 根据图片比例设置平面尺寸

      const material = new THREE.MeshBasicMaterial({
        map: texture,
        transparent: true, // 允许透明
        opacity: 1, // 设置不透明度（0.0为完全透明，1.0为完全不透明）
        side: THREE.DoubleSide, // 设置材质为双面
        blending: THREE.NormalBlending, // 使用正常混合模式
        // depthTest: false, // 禁用深度测试
        // depthWrite: false // 禁止写入深度缓冲
        // metalness: 0, // 设置金属度为 0.5
        // alphaTest: 0.1,// 透明度阈值
        // color: 0xffffff, // 设置材质颜色
      });
      material.map.colorSpace = THREE.SRGBColorSpace;

      const plane = new THREE.Mesh(geometry, material);
      plane.material.depthWrite = false;// 该平面在渲染时会写入深度缓冲区

      plane.position.set(obj.x, obj.y, obj.z);
      plane.renderOrder = obj.renderOrder || 0; // 设置渲染顺序
      plane.userData = obj.labelObj;

      scene.add(plane);

      if (obj.labelObj) {
        if (obj.labelObj.type === 'machine_1') {
          // middle_shebei[obj.labelObj.name] = plane;
          createLabel(plane, { ...obj.labelObj });
        } else if (obj.labelObj.type === 'machine_2') {
          // middle_shebei[obj.labelObj.name] = plane;
          createLabel(plane, { ...obj.labelObj });
        }
      }
    }

    function createLabel(plane, data) {
      // object.geometry.computeBoundingBox(); // 计算包围盒
      const labelDiv = document.createElement('div');
      labelDiv.className = 'label-wrapper';
      labelDiv.innerHTML = `
                <div class="label-bottom"></div>
                <div class="label-line-1"></div>
                <div class="label-line-2"></div>
                <div class="label-content">
                    <h3 class="label-name">${data.name}</h3>
                    <div class="label-item">进程1:xxxx</div>
                    <div class="label-item">进程2:xxxx</div>
                    <div class="label-item">进程3:xxxx</div>
                    <div class="label-item">进程4:xxxx</div>
                </div>
            `;
      document.getElementById('labels').appendChild(labelDiv);
      const labelObject = new CSS3DObject(labelDiv);

      const p_x = (data.position || [])[0] || 0;
      const p_y = (data.position || [])[1] || 0;
      const p_z = (data.position || [])[2] || 0;
      labelObject.position.set(p_x, p_y, p_z);
      labelObject.scale.set(cur_ratio, cur_ratio, cur_ratio);
      middle_shebei[data.name + '_label'] = labelObject;
      scene.add(labelObject);

      // 检查labelsArr是否存
      const find_index = labelsArr.findIndex(item => item.data.uuid === data.uuid);
      if (find_index === -1) {
        labelsArr.push({
          labelObject: labelObject,
          plane: plane,
          data: data,
        })
      } else {
        labelsArr[find_index] = {
          labelObject: labelObject,
          plane: plane,
          data: data,
        }
      }
    }


    // 更新标签位置函数
    function updateLabelPositions() {
      labelsArr.forEach(obj => {
        const vector = new THREE.Vector3();
        obj.plane.getWorldPosition(vector);
        if (obj.labelObject) {
          obj.labelObject.position.copy(vector);
          obj.labelObject.lookAt(camera.position);
        }
      });
    }

    // 动画循环
    function animate() {
      requestAnimationFrame(animate);

      updateLabelPositions();// 更新标签位置

      controls.update(); // 更新控制器
      // 渲染场景
      renderer.render(scene, camera);
      cssRenderer.render(scene, camera);
    }

    animate();

    // 窗口大小调整事件监听器
    window.addEventListener('resize', () => {
      const width = window.innerWidth;
      const height = window.innerHeight;
      renderer.setSize(width, height);
      cssRenderer.setSize(width, height);
      camera.aspect = width / height;
      camera.updateProjectionMatrix();
    });

    window.addEventListener('contextmenu', onContextMenu, false);
    window.addEventListener('dblclick', onDoubleClick, false);
    window.addEventListener('click', hideContextMenu, false);

    function onContextMenu(event) {
      event.preventDefault();
      handleMouseEvent(event, showContextMenu);
    }

    function onDoubleClick(event) {
      event.preventDefault();
      handleMouseEvent(event, showContextMenu);
    }

    function handleMouseEvent(event, callback) {
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

      raycaster.setFromCamera(mouse, camera);
      const intersects = raycaster.intersectObjects(scene.children);

      if (intersects.length > 0) {
        const intersectedObject = intersects[0].object;
        callback(event, intersectedObject.userData);
      }
    }

    function showContextMenu(event, userData) {
      hideContextMenu();

      const menu = document.createElement('div');
      menu.className = 'context-menu';
      menu.style.top = `${event.clientY}px`;
      menu.style.left = `${event.clientX}px`;

      console.log(`a0004-shebei_CSS3D.html 340 [userData]`, userData);
      if (userData && userData.type) {
        const editOption = document.createElement('div');
        editOption.className = 'context-menu-item';
        editOption.innerText = 'Edit';
        editOption.onclick = function () {
          editPlane(userData);
        };
        menu.appendChild(editOption);

        const deleteOption = document.createElement('div');
        deleteOption.className = 'context-menu-item';
        deleteOption.innerText = 'Delete';
        deleteOption.onclick = function () {
          deletePlane(userData);
        };
        menu.appendChild(deleteOption);

        const addChildOption = document.createElement('div');
        addChildOption.className = 'context-menu-item';
        addChildOption.innerText = 'Add Child';
        addChildOption.onclick = function () {
          addChildPlane(userData);
        };
        menu.appendChild(addChildOption);
      }

      document.body.appendChild(menu);
    }

    function hideContextMenu() {
      const menus = document.querySelectorAll('.context-menu');
      menus.forEach(menu => menu.remove());
    }

    function editPlane(userData) {
      console.log('Editing plane:', userData);
    }

    function deletePlane(userData) {
      console.log('Deleting plane:', userData);
    }

    function addChildPlane(userData) {
      console.log('Adding child to plane:', userData);
    }
  </script>
</body>

</html>